﻿using PKTools.Database;
using System.Collections.Generic;
using System.Threading;

namespace PKTools.Scheduler
{
    public abstract class TaskExecutorConfig
    {
        public readonly int
            NoOfWorkers = 4,
            WorkerSleep = 10,
            WatchDogSleep = 60;

        public abstract Sql SqlCreate(bool openTransaction = false, string connection = null);

        public override string ToString()
        {
            return string.Format("workers {0}, worker sleep {1} [s], watch dog sleep {2} [s]", this.NoOfWorkers, this.WorkerSleep, this.WatchDogSleep);
        }
    }

    public sealed class TaskExecutor
    {
        readonly ITaskDispatcher
            dispatcher;
        readonly TaskExecutorConfig
            config;

        readonly TaskWorker[]
            workers;
        readonly ManualResetEvent
            stopEvent;
        readonly Thread
            watchDogThread;

        public TaskExecutor(ITaskDispatcher dispatcher, TaskExecutorConfig config)
        {
            this.dispatcher = dispatcher;
            this.config = config;
            
            this.stopEvent = new ManualResetEvent(false);

            this.watchDogThread = new Thread(WatchDog);

            this.workers = new TaskWorker[this.config.NoOfWorkers];

            Log.Entry(LogSeverity.Info, "scheduler init, {0}", this.config);
        }

        TaskWorker CreateWorker(int id)
        {
            return new TaskWorker(id, this.dispatcher, this.stopEvent, this.config);
        }

        public void Start()
        {
            Log.Entry(LogSeverity.Info, "scheduler start");

            for (int i = 0; i < this.config.NoOfWorkers; i++)
                this.workers[i] = CreateWorker(i);

            this.watchDogThread.Start();
        }

        public void Stop()
        {
            Log.Entry(LogSeverity.Info, "scheduler stop");

            this.stopEvent.Set();

            this.watchDogThread.Join(1000);

            foreach (var worker in this.workers)
                worker.Thread.Join(10000);
        }

        void WatchDog()
        {
            for (; ; )
            {
                if (this.stopEvent.WaitOne(1000 * this.config.WatchDogSleep))
                {
                    Log.Entry(LogSeverity.Debug, "watch dog stop");
                    return;
                }

                Log.Entry(LogSeverity.Debug, "watch dog sniffing");

                for (int i = 0; i < this.config.NoOfWorkers; i++)
                    if (this.workers[i].Thread.ThreadState == ThreadState.Stopped)
                    {
                        Log.Entry(LogSeverity.Warning, "restarting worker {0}", i);

                        this.workers[i] = CreateWorker(i);
                    }
            }
        }
    }
}
